#include "util.h"

#include <assert.h>
#include <limits.h>
#include <pthread.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

#include <atomic>
#include <ctime>
#include <thread>

#include "logger.h"
#include "once_token.h"

bool is_safe(uint8_t b) { return b >= ' ' && b < 128; }

std::string hexdump(const void* buf, size_t len) {
    std::string ret("\r\n");
    char tmp[8];
    const uint8_t* data = (const uint8_t*)buf;
    for (size_t i = 0; i < len; i += 16) {
        for (int j = 0; j < 16; ++j) {
            if (i + j < len) {
                int sz = snprintf(tmp, sizeof(tmp), "%.2x ", data[i + j]);
                ret.append(tmp, sz);
            } else {
                int sz = snprintf(tmp, sizeof(tmp), "   ");
                ret.append(tmp, sz);
            }
        }
        for (int j = 0; j < 16; ++j) {
            if (i + j < len) {
                ret += (is_safe(data[i + j]) ? data[i + j] : '.');
            } else {
                ret += (' ');
            }
        }
        ret += ('\n');
    }
    return ret;
}

std::string exePath(bool isExe /*= true*/) {
    DG_UNUSED(isExe);

    char buffer[PATH_MAX * 2 + 1] = {0};
    int n = readlink("/proc/self/exe", buffer, sizeof(buffer));

    std::string filePath;
    if (n <= 0) {
        filePath = "./";
    } else {
        filePath = buffer;
    }

    return filePath;
}

std::string exeDir(bool isExe /*= true*/) {
    auto path = exePath(isExe);
    return path.substr(0, path.rfind('/') + 1);
}

std::string exeName(bool isExe /*= true*/) {
    auto path = exePath(isExe);
    return path.substr(path.rfind('/') + 1);
}

std::vector<std::string> split(const std::string& s, const char* delim) {
    std::vector<std::string> ret;
    size_t last = 0;
    auto index = s.find(delim, last);
    while (index != std::string::npos) {
        if (index - last > 0) {
            ret.push_back(s.substr(last, index - last));
        }
        last = index + strlen(delim);
        index = s.find(delim, last);
    }
    if (!s.size() || s.size() - last > 0) {
        ret.push_back(s.substr(last));
    }
    return ret;
}

bool start_with(const std::string& str, const std::string& substr) {
    return str.find(substr) == 0;
}

bool end_with(const std::string& str, const std::string& substr) {
    auto pos = str.rfind(substr);
    return pos != std::string::npos && pos == str.size() - substr.size();
}

static std::string limitString(const char* name, size_t max_size) {
    std::string str = name;
    if (str.size() + 1 > max_size) {
        auto erased = str.size() + 1 - max_size + 3;
        str.replace(5, erased, "...");
    }
    return str;
}

std::string getTimeStr(const char* fmt, time_t time) {
    if (!time) {
        time = ::time(nullptr);
    }
    auto tm = getLocalTime(time);
    size_t size = strlen(fmt) + 64;
    std::string ret;
    ret.resize(size);
    size = std::strftime(&ret[0], size, fmt, &tm);
    if (size > 0) {
        ret.resize(size);
    } else {
        ret = fmt;
    }
    return ret;
}

struct tm getLocalTime(time_t sec) {
    struct tm tm;
#if 0
            struct tm *tblock;
            tblock = localtime(&sec);
            memcpy(&tm, tblock, sizeof(struct tm));
#else
    localtime_r(&sec, &tm);
#endif
    return tm;
}

long getGMTOff(bool force) {
    static bool init = false;
    static long gmtoff = 0;
    if (!init || force) {
        time_t t = time(NULL);
        struct tm* tp = localtime(&t);
        gmtoff = tp->tm_gmtoff;
    }

    return gmtoff;
}

static inline uint64_t getCurrentMicrosecondOrigin() {
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    return tv.tv_sec * 1000000LL + tv.tv_usec;
}

static std::atomic<uint64_t> s_currentMicrosecond(0);
static std::atomic<uint64_t> s_currentMillisecond(0);
static std::atomic<uint64_t> s_currentMicrosecond_system(
    getCurrentMicrosecondOrigin());
static std::atomic<uint64_t> s_currentMillisecond_system(
    getCurrentMicrosecondOrigin() / 1000);

static inline bool initMillisecondThread() {
    static std::thread s_thread([]() {
        setThreadName("stamp thread");
        uint64_t last = getCurrentMicrosecondOrigin();
        uint64_t now;
        uint64_t microsecond = 0;
        while (true) {
            now = getCurrentMicrosecondOrigin();
            // 记录系统时间戳，可回退
            s_currentMicrosecond_system.store(now, std::memory_order_release);
            s_currentMillisecond_system.store(now / 1000,
                                              std::memory_order_release);

            // 记录流逝时间戳，不可回退
            int64_t expired = now - last;
            last = now;
            if (expired > 0 && expired < 1000 * 1000) {
                // 流逝时间处于0~1000ms之间，那么是合理的，说明没有调整系统时间
                microsecond += expired;
                s_currentMicrosecond.store(microsecond,
                                           std::memory_order_release);
                s_currentMillisecond.store(microsecond / 1000,
                                           std::memory_order_release);
            } else if (expired != 0) {
                WarnL << "Stamp expired is abnormal:" << expired;
            }
            // 休眠0.5 ms
            usleep(500);
        }
    });
    static onceToken s_token([]() { s_thread.detach(); });
    return true;
}

uint64_t getCurrentMillisecond(bool system_time) {
    static bool flag = initMillisecondThread();
    DG_UNUSED(flag);

    if (system_time) {
        return s_currentMillisecond_system.load(std::memory_order_acquire);
    }
    return s_currentMillisecond.load(std::memory_order_acquire);
}

uint64_t getCurrentMicrosecond(bool system_time) {
    static bool flag = initMillisecondThread();
    DG_UNUSED(flag);

    if (system_time) {
        return s_currentMicrosecond_system.load(std::memory_order_acquire);
    }
    return s_currentMicrosecond.load(std::memory_order_acquire);
}

void setThreadName(const char* name) {
    assert(name);
    pthread_setname_np(pthread_self(), limitString(name, 16).data());
}

std::string getThreadName() {
    std::string ret;
    ret.resize(32);
    auto tid = pthread_self();
    pthread_getname_np(tid, (char*)ret.data(), ret.size());
    if (ret[0]) {
        ret.resize(strlen(ret.data()));
        return ret;
    }
    return std::to_string((uint64_t)tid);
}

bool setThreadAffinity(int i) {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    if (i >= 0) {
        CPU_SET(i, &mask);
    } else {
        for (auto j = 0u; j < std::thread::hardware_concurrency(); ++j) {
            CPU_SET(j, &mask);
        }
    }
    if (!pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask)) {
        return true;
    }
    WarnL << "pthread_setaffinity_np failed:" << errno;

    return false;
}

#define HAS_CXA_DEMANGLE 1
#if HAS_CXA_DEMANGLE
#include <cxxabi.h>
#endif

std::string demangle(const char* mangled) {
    int status = 0;
    char* demangled = nullptr;
#if HAS_CXA_DEMANGLE
    demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status);
#endif
    std::string out;
    if (status == 0 && demangled) {  // Demangling succeeeded.
        out.append(demangled);
        free(demangled);
    } else {
        out.append(mangled);
    }
    return out;
}

#if ENABLE_UUID
std::string genUUID() {
    uuid_t uuid;
    uuid_generate(uuid);
    char uuid_str[37];  // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
    uuid_unparse_lower(uuid, uuid_str);
    return std::string(uuid_str);
}
#endif